#pragma once

#include <zenoui/Rect.hpp>
#include <zenoui/Shape.hpp>
#include <zenoui/Property.hpp>
#include <zenoui/Event.hpp>
#include <zenox/pragma.hpp>
#include <zenox/memory.hpp>
#include <optional>
#include <concepts>
#include <vector>
#include <memory>

namespace zenoui {

struct Widget {
public:
    Widget();
    Widget(Widget &&) = delete; // disable move and copy
    virtual ~Widget();

    void do_handle(Event *event); // external interface for event handling (recursively send to children until event accepted)
    void do_direct_handle(Event *event); // directly handle the event by this widget, no recursively send to children, no rect check
    void do_paint(Rect const &parent_rect, Transform const &xform, int const *viewdims); // external interface for painting (recursively paints child widgets as well)
    void paint_debug_rect(); // for debug purpose, show HTML-like borders

protected:
    virtual void paint() = 0; // internal virtual function for painting current widget

#define _FUNCTION(_EventType) \
    virtual void handle(_EventType *event);
ZENO_FOREACH_EVENT_TYPE(_FUNCTION) // invoke the X-macro to define several virtual functions for all event types
#undef _FUNCTION

#define _FUNCTION(_PropType) \
    virtual void property(_PropType *prop);
ZENO_FOREACH_PROPERTY_TYPE(_FUNCTION) // invoke the X-macro to define several virtual functions for all property types
#undef _FUNCTION

    template <class DerivedEvent>
    friend struct EventCRTP; // allow this class template to access Widget's protected handle function

    template <class Derivedproperty, class ValueType>
    friend struct PropertyCRTP; // allow this class template to access Widget's protected handle function

    friend struct DOMBuilder; // allow DOMBuilder to directly write into m_shape with out updating rect too frequently

public:
    std::vector<Widget *> children_list() const; // returns a list of children of this widget (non-owning raw pointer)
    void remove_child(Widget *child); // removing an existing child, the pointer will be invalid after remove
    Widget *add_child(zenox::shared_ptr<Widget> child); // append a child widget to list, will updates every other children's rect
    void batched_add_child(std::vector<zenox::shared_ptr<Widget>> children); // append multiple child widgets at once
    template <std::derived_from<Widget> DerivedWidget, class ...Ts> requires std::constructible_from<DerivedWidget, Ts...>
    DerivedWidget *add_child(Ts &&...ts) { // equivalent to add_child(zenox::make_shared<DerivedWidget>())
        return static_cast<DerivedWidget *>(add_child(zenox::make_shared<DerivedWidget>(std::forward<Ts>(ts)...)));
    }

    void update_rect(Widget *child = nullptr); // recalculates sizes and margins into pre-calculated rects for all children, if child is non-null, then i will only update this child for better speed
    void update_rect(Rect const &new_rect) { // used internally by Game::framebuffer_resize_callback, do not set manually
        m_rect = new_rect;
        update_rect();
    }

    std::optional<Transform> local_transform() const; // transform from this widget to child (equivalent to m_shape.transform)
    Transform global_transform() const; // transform from global coordinate to this widget

    Rect rect() const { // get the (pre-calculated) rectangle boundary of this widget
        return m_rect;
    }
    Rect event_rect() const { // get the rectangle boundary that can recieve event from (aka event horizon!!!)
        Rect erect = m_rect;
        erect.extendMargin(m_shape.event_margin);
        return erect;
    }
    Rect global_rect() const; // same as rect(), but in global coordinate system
    Rect inner_rect() const; // same as rect(), but subtracts padding (in shape) away
    Widget *child_at(glm::vec2 const &pos) const; // find child widget at position, return null if not found

    Shape const &shape() const { // get current widget shape (contains information about sizes and margins)
        return m_shape; // caution: do not even try const_cast, deep-copy + modify + set_shape instead
    }

    Widget *parent() const { // get parent widget, may return null for the root widget
        return m_parent;
    }

    void set_shape(Shape const &new_shape) { // set to a new shape (sizes and margins), updates rect as well
        m_shape = new_shape;
        if (m_parent) [[likely]] {
            m_parent->update_rect(this);
        } else {
            update_rect();
        }
    }

    template <std::invocable<Shape &> Fn>
    void modify_shape(Fn &&shape_mod) { // modify shape inplace (sizes and margins), updates rect as well
        shape_mod(m_shape);
        if (m_parent) [[likely]] {
            m_parent->update_rect(this);
        } else {
            update_rect();
        }
    }

private:
    Rect m_rect; // the actual rect of this widget, automatically updated when window resize, do not set manually
    Widget *m_parent = nullptr; // non-owning pointer to parent, parent must hold current widget in m_children
    std::vector<zenox::shared_ptr<Widget>> m_children; // child widgets list, wrapped by owning pointer to support polymorphism
    Shape m_shape; // the user specified size attribute of the widget, can be set manually to tweak margin/padding/size of the window
};

template <class DerivedEvent>
void EventCRTP<DerivedEvent>::send_to(Widget *to_widget) { // declaration in Event.hpp
    static_assert(std::is_base_of_v<EventCRTP, DerivedEvent>);
    to_widget->handle(static_cast<DerivedEvent *>(this)); // this is a typical visitor pattern implemented via CRTP
}

template <class DerivedProperty, class ValueType>
void PropertyCRTP<DerivedProperty, ValueType>::get_or_set(Widget *to_widget) { // declaration in Property.hpp
    static_assert(std::is_base_of_v<PropertyCRTP, DerivedProperty>);
    to_widget->property(static_cast<DerivedProperty *>(this)); // this is a typical visitor pattern implemented via CRTP
}

}
